#include "list.h"
#include <stdlib.h>
#include <stdio.h>

/**
 * Initialise une liste vide
 */
void list_initialize(List* list, const Object *object, int copymode)
{
    list->first = NULL;
    list->last = NULL;
    list->count = 0;
    list->object = object;
    list->copymode = copymode;
}

/**
 * Initialise une copie de la liste src dans la liste dest
 */
void list_initializeCopy(List* list, const List* source)
{
    Cell* cell = source->first;
    list_initialize(list,source->object,source->copymode);
    while(cell != NULL)
    {
        list_insertLast(list,cell->value);
        cell = cell->next;
    }
}

/**
 * Désaloue l'ensemble des ressources de la liste passée en argument
 */
void list_destroy(List** list)
{
    list_clear(*list);
    free(*list);
    *list = NULL;
}

/**
 * Copie en mémoire la liste src dans la list dest passé en argument
 */
void list_copy(List* list, const List* source)
{
    list_clear(list);
    list_initializeCopy(list,source);
}

/**
 * Supprimme l'ensemble des cellule contenues dans la liste passée en argument
 */
void list_clear(List* list)
{
    if(list != NULL)
    {
        Cell* cell = list_getFirst(list);
        while (cell != NULL)
        {
            list->first = cell->next;
            if((list->object != NULL) && (list->copymode == 1))
                list->object->destroy(&cell->value);
            cell_destroy(&(cell));
            cell = list->first;
        }
        list->first = NULL;
        list->last = NULL;
        list->count = 0;
    }
}

/**
 * Obtient le nombre de cellules contenue dans la liste passée en argument
 */
int list_getCount(const List* list)
{
    return list->count;
}

/**
 * Obtient l'adresse de la première cellule de la liste passée en argument
 */
Cell* list_getFirst(const List* list)
{
    return list->first;
}

/**
 * Obtient la valeur de la première cellule de la liste passée en argument
 */
const void *list_getFirstValue(const List* list)
{
    return list_getFirst(list)->value;
}

void* list_getFirstValueCopie(const List* list)
{
    void* ret = NULL;
    if((list->object != NULL) && (list->copymode == 1))
    {
        list->object->create(&ret,NULL);
        list->object->copy(list_getFirst(list)->value,ret);
    }
    return ret;
}

/**
 * Obtient l'adresse de la dernière cellule de la liste passée en argument
 */
Cell* list_getLast(const List* list)
{
    return list->last;
}

/**
 * Obtient la valeur de la dernière cellule de la liste passée en argument
 */
const void *list_getLastValue(const List* list)
{
    return list_getLast(list)->value;
}

void* list_getLastValueCopie(const List* list)
{
    void* ret = NULL;
    if((list->object != NULL) && (list->copymode == 1))
    {
        list->object->create(&ret,NULL);
        list->object->copy(list_getLast(list)->value,ret);
    }
    return ret;
}

/**
 * Obtient l'adresse de la cellule dans la liste à l'index passé en argument
 * @param list L'adresse de la liste
 * @return L'adresse de la cellule à l'index passée en argument ou NULL si non trouvé
 */
Cell* list_getAt(const List* list, int index)
{
    if((list->count > index) && (index >= 0))
    {
        int i;
        Cell* cell = list_getFirst(list);
        for(i = 0; i < index; i++)
            cell = cell->next;
        return cell;
    }
    else
        return NULL;
}

/**
 * Obtient la valeur de la cellule dans la liste à l'index passé en argument
 * @param list L'adresse de la liste
 * @return L'adresse de la cellule à l'index passée en argument ou NULL si non trouvé
 */
const void *list_getValueAt(const List* list, int index)
{
    Cell* cell = list_getAt(list,index);
    if(cell != NULL)
        return list_getAt(list,index)->value;
    else
        return NULL;
}

void* list_getValueAtCopie(const List* list, int index)
{
    void* ret = NULL;
    if((list->object != NULL) && (list->copymode == 1))
    {
        list->object->create(&ret,NULL);
        list->object->copy(list_getAt(list,index)->value,ret);
    }
    return ret;
}

/**
 * Supprime la cellule dans la liuste à l'index passé en argument
 * @param list L'adresse de la liste
 * @return 0 si l'index ne fait pas parti de la liste, 1 sinon
 */
int list_removeAt(List* list, int index)
{
    if(list->count > index)
    {
        int i;
        Cell *previous = NULL,  *cell = list_getFirst(list);
        for(i = 0; i < index; i++)
        {
            previous = cell;
            cell = cell->next;
        }
        if(list->last == cell)
            list->last = previous;
        if(previous == NULL)
            list->first = cell->next;
        else
            previous->next = cell->next;
        if((list->object != NULL) && (list->copymode == 1))
            list->object->destroy(&cell->value);
        cell_destroy(&cell);
        list->count--;
        return i;
    }
    else
        return -1;
}

void list_removeFirst(List* list)
{
    list_removeAt(list,0);
}

void list_removeLast(List* list)
{
    list_removeAt(list,list->count - 1);
}

/**
 * Obtient l'adresse de la prémière cellule de la liste dont l'élément est égale à celui passé en argument
 * @param list L'adresse de la liste
 * @param cell Resultat : l'adresse de la cellule trouvée, ou NULL si aucun élément correspondant n'a été trouvé
 * @return l'index de la cellule trouvée, ou -1 si aucun élément correspondant n'a été trouvé
 */
int list_get(const List* list, void* value, Cell** cell)
{
    int i = 0;
    (*cell) = list_getFirst(list);
    while ((*cell) != NULL)
    {
        (*cell) = cell_getNext((*cell));
        if((*cell)->value == value)
            return i;
        i++;
    }
    return -1;
}

int list_getFirstOf(const List* list, void* value, Cell** cell)
{
    int i = 0;
    Cell* _cell;
    if(cell == NULL)
        cell = &(_cell);
    if(list->object != NULL)
    {
        (*cell) = list_getFirst(list);
        while ((*cell) != NULL)
        {
            if(list->object->equals((*cell)->value,value))
                return i;
            (*cell) = cell_getNext((*cell));
            i++;
        }
    }
    return -1;
}

int list_contains(const List* list, void* value)
{
    return (list_getFirstOf(list,value,NULL) != -1);
}

/**
 * Supprime la cellule de la liste dont l'élément est égale à celui passé en argument
 * @return 1 si la cellule a été supprimée, 0 si aucun élément correspondant n'a été trouvé
 */
int list_remove(List* list, void* value)
{
    int i = 0;
    Cell *previous = NULL, *cell = list_getFirst(list);
    while(cell != NULL)
    {
        if(cell->value == value)
        {
            if(list->last == cell)
                list->last = previous;
            if(previous == NULL)
                list->first = cell->next;
            else
                previous->next = cell->next;
            if((list->object != NULL) && (list->copymode == 1))
                list->object->destroy(&cell->value);
            cell_destroy(&cell);
            list->count--;
            return i;
        }
        previous = cell;
        cell = cell->next;
        i++;
    }
    return 0;
}

int list_removeFirstOf(List* list, void* value)
{
    if(list->object != NULL)
    {
        int i = 0;
        Cell *previous = NULL, *cell = list_getFirst(list);
        while(cell != NULL)
        {
            if(list->object->equals(cell->value,value))
            {
                if(list->last == cell)
                    list->last = previous;
                if(previous == NULL)
                    list->first = cell->next;
                else
                    previous->next = cell->next;
                if(list->copymode == 1)
                {
                    list->object->destroy(&cell->value);
                }
                cell_destroy(&cell);
                list->count--;
                return i;
            }
            previous = cell;
            cell = cell->next;
            i++;
        }
    }
    return -1;
}

/**
 * Insère l'élément dans la liste à l'index passée en argument
 * @return
 */
void* list_insertAt(List* list, void* value, int index)
{
    void* ret = NULL;
    Cell *previous = list_getAt(list,index), *cell;
    cell_create(&cell);
    if((list->object != NULL) && (list->copymode == 1))
    {
        list->object->create(&ret,NULL);
        list->object->copy(value,ret);
    }
    else
        ret = value;
    cell->value = ret;
    if(list->count < index) /*Si l'index est supérieure au nombre d'élément on le normalise au nombre d'éléments*/
    {
        index = list->count;
    }
    if(list->count > 0)
    {
        if(index == list->count)
        {
            cell->next = NULL;
            list->last->next = cell;
            list->last = cell;
        }
        else if(index == 0)
        {
            cell->next = list->first;
            list->first = cell;
        }
        else
        {
            cell->next = previous->next;
            previous->next = cell;
        }
    }
    else
    {
        cell->next = NULL;
        list->first = cell;
        list->last = cell;
    }
    list->count++;
    return ret;
}

/**
  * Insère l'élément en première position de la liste passée en argument
  */
void *list_insertFirst(List* list, void* value)
{
    return list_insertAt(list,value,0);
}

/**
 * Insère l'élément en dernière position de la liste passée en argument
 */
void* list_insertLast(List* list, void* value)
{
    return list_insertAt(list,value,list_getCount(list));
}

int list_equals(const List* a, const List* b)
{
    int i; int ret = 0;
    if(a->count == b->count)
    {
        if((a->object != NULL) && (b->object != NULL))
        {
            if(object_equals(a->object, b->object) )
            {
                ret = 1;
                for(i = 0; i < a->count; i ++)
                {
                    if(a->object->equals(list_getValueAt(a,i),list_getValueAt(b,i)))
                    {
                        ret = 0;
                        break;
                    }
                }
            }
        }
        else
        {
            if((a->object == NULL) && (b->object == NULL))
            {
                ret = 1;
                for(i = 0; a->count; i++)
                {
                    if(list_getValueAt(a,i) == list_getValueAt(b,i))
                    {
                        ret = 0;
                        break;
                    }
                }
            }
        }
    }
    return ret;
}
